import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;


public class WhoEncode0001 {

	/** 已读取字节数 */
	private int readCount;
	
	/** 已输出字节数 */
	private int outCount = 0;

	/** 当前字节已解码/编码位数 */ 
	private int bitCount;
	
	/** 解码字典,游戏原文件的[10H~10FH],256字节 */
	private byte[] decDict = new byte[0x100];

	/** 编码字典/解码字典翻转 */
	private byte[] encDict = new byte[0x100];

	/** 压缩数据长度,[110H] */
	private int binSize;
	
	/** 原文长度,[114H] */
	private int textSize;
	
//	/** 压缩数据在文件中的偏移,[118H],值都是0x120 */
//	private int pos = 0x120;
	
	/** 原文 */
	private byte[] text;
	
	/** 压缩数据（缓冲区大小为textSize，实际输出时只输出前binSize） */
	private byte[] outBin;
	
	private byte[] ls11head = {0x4C,0x53,0x31,0x31,0,0,0,0,0,0,0,0,0,0,0,0};
	
	private byte[] infoLine = new byte[0x10];

	private boolean stopMe;
	
	public void encode() {
		stopMe = false;
		File[] files = MainForm.openFiles("./decoded", "uni");
		File foutdir = null;		
		if (files.length == 0) {
//			System.exit(0);
			MainForm.showMsgBox("not selected");
			return;
		} else {
			if (files[0].getParent().endsWith("decoded")) {
				foutdir = new File(files[0].getParent().substring(0,files[0].getParent().length() - 7)+ "/encoded/");
			} else {
				foutdir = new File(files[0].getParent() + "/encoded/");
			}
			foutdir.mkdir();
		}

		FileInputStream fis = null;
		DataInputStream dis = null;
		FileOutputStream fos = null;
		try {
			for (int file_i=0; file_i<files.length; file_i++) {
				if(stopMe) return;
				MainForm.setProgressBar(files[file_i].getName(), file_i * 3);
			
				/******** 扩容,更新文件头索引 ********/
				RandomAccessFile raf = new RandomAccessFile(files[file_i], "rw");
				
				/** 文件中有几条文本 */
				short textCount = Util.convByteOrderShort(raf.readUnsignedShort());
				/** 文本索引 */
				int[] textIndex = new int[textCount];
				/** 文本索引数组的下标 */
				int p = 0;

				raf.skipBytes(textCount * 4);
				textIndex[p++] = (int)raf.getFilePointer();
				
				short s0 = 0, s1 = 0, s2 = 0;
				/** 一个文本数组中剩余文本条数 */
				int count_textArray = 0;
				for (int i=(int)raf.getFilePointer(); p<textCount; i+=2) {
//				for (int i=(int)raf.getFilePointer(); i<raf.length()-10; i+=2) {
					s0 = raf.readShort();
					if (s0 == 0x0500) {
						i+=2;
						if(raf.readShort() == 0x0500) {
							i+=2;
							s2 = raf.readShort();
							if(s2 == 0x0500) {	//05 00 05 00 05 00, "555", 一条文本结束的分隔符
								if(count_textArray>0) {
									count_textArray--;
								} else {
									textIndex[p++] = (int)raf.getFilePointer();
								}
							} else if (s2 == 0x0400) {	
								//文本以05 00 05 00 04 00 02 00开头 , "5542",
								//表示此条文本是在一个文本数组里, 此条文本的"555"分隔符不记入文件头索引中
								//记录索引时, 每出现一个"5542", 就跳过一个"555"
								//经常连续出现: "555 5542 xxxx 555 5542 xxxxx 555 5542 xxx 555 xxxx 555"
								//也有嵌套的: "5542 xx 5542 xxxxx 555 xxx 555 xxxxxxx 555"
								//以上两种情况, 在文件头中只记为一条文本（文本数组）
								//5542, 多为同一位置不同分支的文本
								//5542之后通常紧跟一个<状态变量> = <数值>的判断条件字符串, 用于游戏解析
								i+=2;
								raf.skipBytes(2);	//02 00
								count_textArray += 1;
							}
						}
					} else if (s0 == 0x0100) {
						i+=2;
						s1 = raf.readShort();
						if (s1 == 0x5300) {
							i+=2;
							s2 = raf.readShort();
							if (s2 == 0x0200) {	//01 00 53 00 02 00 CA 00 25 00 数字,
								//数字是unicode编码的文本形式的字符串数字, 非字节的数值,
								//如33 00 30 00, 是"3"和"0"这两个字符,
								//表示这是一个有30条文本的数组, 要跳过30个"555", 在文件头索引中只记为一条文本 
								i+=4;
								raf.skipBytes(4);
								int num=0;
								i+=2;
								num = Util.convByteOrderShort(raf.readUnsignedShort()) - 0x30;
								while (num >= 0 && num <= 9) {
									count_textArray = count_textArray * 10 + num;
									i+=2;
									num = Util.convByteOrderShort(raf.readUnsignedShort()) - 0x30;
								}
								count_textArray--;
							}
						}
					}
				}
					//写入覆盖原索引
				raf.seek(2);
				for(int i=2,j=0; i<textIndex[0]; i+=4,j++) {
					raf.writeInt(Util.convByteOrderInt(textIndex[j]));
				}
				raf.close();
				
				/*********** 编码 ***********/
				
				String foutName = foutdir + "/" + files[file_i].getName().substring(0, files[file_i].getName().length()-3) + "bin";
				File fout = new File(foutName);
				if (fout.exists()) {
					fout.delete();
				}
				
				text = new byte[(int) files[file_i].length()];
				fis = new FileInputStream(files[file_i]);
				fis.read(text);
				fis.close();
				textSize = text.length;
				
				decDict = makeDict(text, 0x100);
				
				for (int i=0; i<0x100; i++) {
					encDict[Util.getUnsignbyte(decDict[i])] = (byte)i;
				}
//				Util.prtArray(encDict);
				outBin = new byte[textSize];
				
				MainForm.setProgressBarValue(file_i *3 +1);
				binSize = encodeText2Bin(text, encDict, true);
				MainForm.setProgressBarValue(file_i *3 +2);
				
				if (binSize > textSize) {
					System.out.println(binSize+","+textSize+"Error!");
				} else {
					infoLine[0] = (byte)((binSize >> 24) & 0xFF);
					infoLine[1] = (byte)((binSize >> 16) & 0xFF);
					infoLine[2] = (byte)((binSize >> 8) & 0xFF); 
					infoLine[3] = (byte)(binSize & 0xFF);
					infoLine[4] = (byte)((textSize >> 24) & 0xFF);
					infoLine[5] = (byte)((textSize >> 16) & 0xFF);
					infoLine[6] = (byte)((textSize >> 8) & 0xFF); 
					infoLine[7] = (byte)(textSize & 0xFF);
					infoLine[10] = (byte)1; 
					infoLine[11] = (byte)0x20;

					fos = new FileOutputStream(fout);
					fos.write(ls11head);
					fos.write(decDict);
					fos.write(infoLine);
					fos.write(outBin,0,binSize);
					fos.close();
					
				}
			}
			MainForm.showMsgBox("done");
		} catch (Exception e) {
			MainForm.showMsgBox(e.getMessage());
			e.printStackTrace();
		} finally {
			try {
				if (dis != null) {dis.close(); dis = null;}
				if (fis != null) {fis.close(); fis = null;}
				if (fos != null) {fos.close(); fos = null;}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/** 统计原文各字节出现频率，生成字典 */
	private byte[] makeDict(byte[] text, int dictLen) {
		int[] pText = new int[dictLen];
		byte[] dictArray = new byte[dictLen];
		int dictIndex = 0;
		for (int i = 0; i < text.length; i++) {
			pText[Util.getUnsignbyte(text[i])]++;
		}
		int idx;
		while (dictIndex < dictLen) {
			idx = 0;
			for (int i=0; i<dictLen; i++) {
				if (pText[i] != -1 && pText[i] > pText[idx]) {
					idx = i;
				}
			}
			dictArray[dictIndex] = (byte) idx;
			pText[idx] = -1;
			dictIndex++;
		}

//		Util.prtArray(dictArray);
		return dictArray;
	}
	
	/** @return outCount */
	private int encodeText2Bin(byte[] text, byte[] encDict, boolean bCompare){
		/** 要编码的字节 */
		int code = 0;
		int off, len, lenmax, offmax;
		
		readCount = 0;
		outCount = 0;
		int textSize = text.length;
		
		if (bCompare) {
			while (readCount < textSize) {
				for (off = 1, lenmax = 0, offmax = 1; off <= readCount; off++) {
					for (len = 0; len + readCount < textSize; len++){
						if(text[readCount - off + len] != text[readCount + len]){
							break;
						}
					}
					if (len > lenmax) {
						lenmax = len;
						offmax = off;
					}
				}
				if (lenmax >= 3) {
					setCode(offmax + 256);
					setCode(lenmax - 3);
					readCount += lenmax;
				} else {
					code = (int) (encDict[Util.getUnsignbyte(text[readCount])]) & 0xFF;
					setCode(code);
					readCount++;
				}
			}
		} else {
			while (readCount < textSize) {
				code = (int) (encDict[Util.getUnsignbyte(text[readCount])]) & 0xFF;
				setCode(code);
				readCount++;
			}
		}
		if (bitCount != 0) {
			outBin[outCount] <<= (8 - bitCount);
			bitCount = 0;
			outCount++;
		}

		return outCount;
	}
	
	private void setCode(int code) {
		int temp, code1, code2, len;
		temp = code;
	    len = 0;
	    code1 = 0;
	    while( temp > 1) {
	        temp >>= 1;
	        code1 = (code1 << 1) | 0x01;
	        len++;
	    }     
	    if( (code1 << 1) <= code ) {
	        len++;
			code1 = code & (~1);
		} else {
			code1--;
		}
	    code2 = code - code1;

	    setBit(code1, len);
	    setBit(code2, len);
	}

	private void setBit(int code, int len) {
		for(int i=len-1; i>=0; i--) {
	        outBin[outCount] = (byte)((outBin[outCount] << 1) | (code >> i));
	        bitCount++;
	        if( bitCount > 7)
	        {
	            bitCount = 0;
	            outCount++;
	        }
	    }
	}

	public void stopMe(){
		stopMe  = true;
	}

}
